"""
可自定义属性的装饰器

问题：
   你想写一个装饰器来包装一个函数，并且允许用户提供参数在运行时控制装饰器行为。
解决方案：
    引入一个访问函数，使用 nonlocal 来修改内部变量。
    然后这个访问函数被作为一个属性赋值给包装函数。
"""

import logging
from functools import wraps, partial


def attach_wrapper(obj, func=None):
    if func is None:
        return partial(attach_wrapper, obj)

    setattr(obj, func.__name__, func)
    return func


def logged(level, name=None, message=None):
    def decorate(func):
        log_name = name if name else func.__module__
        log = logging.getLogger(log_name)
        log_msg = message if message else func.__name__

        @wraps(func)
        def wrapper(*args, **kwargs):
            log.log(level, log_msg)
            return func(*args, *kwargs)

        @attach_wrapper(wrapper)
        def set_level(new_level):
            nonlocal level
            level = new_level

        @attach_wrapper(wrapper)
        def set_message(new_msg):
            nonlocal log_msg
            log_msg = new_msg

        return wrapper

    return decorate


@logged(logging.DEBUG)
def add(x, y):
    return x + y


@logged(logging.INFO, "user")
def say_hello(name):
    print("hello", name)


def use_logged():
    logging.basicConfig(level=logging.DEBUG)
    result = add(1, 1)
    print("1 + 1 = ", result)
    add.set_message("add called")
    result = add(1, 1)
    print("1 + 1 = ", result)
    add.set_level(logging.WARNING)
    result = add(1, 1)
    print("1 + 1 = ", result)
    # 输出结果：
    """
    DEBUG:__main__:add
    DEBUG:__main__:add called
    WARNING:__main__:add called
    1 + 1 =  2
    1 + 1 =  2
    1 + 1 =  2
    """


if __name__ == '__main__':
    use_logged()
